﻿using Arction.Wpf.Charting;
using Arction.Wpf.Charting.ChartManager;
using Arction.Wpf.Charting.Series3D;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

using System.Windows.Media;

using System.Windows.Shapes;
using System.Windows.Threading;
using WPF_UI_Lightningchart.ViewModels;
using Panel = System.Windows.Controls.Panel;
using UserControl = System.Windows.Controls.UserControl;

namespace WPF_UI_Lightningchart.UserControls
{
    /// <summary>
    /// _3DSpectrumControl.xaml 的交互逻辑
    /// </summary>
    public partial class Spectrum3DControl : UserControl
    {
        private SensorViewModel SensorViewModel;
        private Spectrogram3D _spectrograms3D;
        private DispatcherTimer _timerAutoYFit;
        private ChartManager _chartManager;

        //参数
        private int _intervalFFT { get; set; } = 1000; // FFT间隔
        private int _channelCount { get; set; } = 1;
        private int _samplingFrequency { get; set; } = 1000; // 采样频率
        private int _resolution { get; set; } = 1024; // FFT窗口长度
        private int _topFrequency { get; set; } = 120; //最大频率


        public Spectrum3DControl(IEventAggregator eventAggregator)
        {
            InitializeComponent();
            this.Loaded += (r, s) =>
            {
                _chartManager = new ChartManager
                {
                    Name = "chartManager",
                    MemoryGarbageCollecting = true
                };
                _timerAutoYFit = new DispatcherTimer
                {
                    Interval = new TimeSpan(0, 0, 0, 0, 500)
                };
                _timerAutoYFit.Tick += new EventHandler(_timerAutoYFit_Tick);
                _timerAutoYFit.IsEnabled = true;
                _resolution = 1024;
                _topFrequency = 120;
                _intervalFFT = 1000;
                InitFFTCharts();
            };
            this.Unloaded += (s, e) =>
            {
                Task.Run(() =>
                {
                    this.Dispatcher.Invoke(() =>
                    {
                        PerformDisposing();
                    });
                });
            };
        }

        private void Grid_DragOver(object sender, DragEventArgs e)
        {
            e.Effects = e.Data.GetDataPresent(typeof(SensorViewModel)) ? DragDropEffects.Move : DragDropEffects.None;
            e.Handled = true;
        }
        private void Grid_Drop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(SensorViewModel)))
            {
                if (SensorViewModel != null)
                {
                    InitFFTCharts();
                    SensorViewModel.FFTProcessor.OnFFTCalculated -= HandleSignalReaderNewPoints;
                }
                var sensorData = e.Data.GetData(typeof(SensorViewModel)) as SensorViewModel;
                SensorViewModel = (SensorViewModel)sensorData;
                SensorViewModel.FFTProcessor.OnFFTCalculated += HandleSignalReaderNewPoints;
            }
        }

        private void HandleSignalReaderNewPoints(double[][][] xValues, double[][][] yValues, double speed)
        {
            Dispatcher.Invoke(new System.Action(() =>
            {
                int channelNumber = 0;
                int rows = xValues.Length;
                for (channelNumber = 0; channelNumber < _channelCount; channelNumber++)
                {
                    if (_spectrograms3D != null)
                    {
                        _spectrograms3D.SetData(yValues, channelNumber, rows, speed);
                    }
                }
            }));
        }

        public void PerformDisposing()
        {
            _timerAutoYFit.Tick -= new EventHandler(_timerAutoYFit_Tick);
            _timerAutoYFit.IsEnabled = false;
            _timerAutoYFit.Stop();
            gridChartGrid?.Children.Clear();
            gridChartGrid = null;
            if (_spectrograms3D != null)
            {
                _spectrograms3D.Dispose();
                _spectrograms3D = null;
            }
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        #region FFT图
        /// <summary>
        /// 初始化FFT
        /// </summary>
        private void InitFFTCharts()
        {
            //清除释放资源

            if (_spectrograms3D != null)
            {
                _spectrograms3D.Dispose();
                _spectrograms3D = null;
            }
            int resolution = _resolution;
            //满足采样定理
            if (_topFrequency < _samplingFrequency / 2)
                resolution = (int)Math.Round((double)_topFrequency / (double)(_samplingFrequency) * _resolution);//FFT显示窗口长度

            Spectrogram3D s3d =
                new Spectrogram3D(
                    gridChartGrid,
                    resolution,
                    _intervalFFT,
                    5,
                    0,
                    _topFrequency,
                    "Power spectrum P(f)\nChannel " + (1).ToString(),
                    //_chart.ViewXY.SampleDataSeries[channelNumber].LineStyle.Color
                    Colors.Red
                );

            s3d.Chart.ChartName = "Spectrum 3D chart " + (1).ToString();

            _spectrograms3D = s3d;

            ArrangeFFTCharts();
        }
        private void ArrangeFFTCharts()
        {
            if (_channelCount == 0)
                return;

            int totalWidth = (int)gridChartGrid.ActualWidth;
            int totalHeight = (int)gridChartGrid.ActualHeight;
            int height = totalHeight / _channelCount;
            int x = 0;
            int top = 0;
            _spectrograms3D.SetBounds();
            top += height;
        }
        private void _timerAutoYFit_Tick(object sender, EventArgs e)
        {
            FitY();
        }
        private void FitY()
        {
            if (_spectrograms3D != null)
            {
                _spectrograms3D.FitView();
            }
        }
        #endregion
    }

    public class Spectrogram3D
    {
        private LightningChartUltimate _chart = null;
        private double m_dCurrentZ = 0.0;
        private double m_dStepZ = 0.0;
        private SurfaceGridSeries3D m_surface = null;
        private WaterfallSeries3D m_waterfallFront = null;

        public Spectrogram3D(
            Panel parentControl,
            int resolution,
            double timeStepMs,
            double timeRangeLengthSec,
            double freqMin,
            double freqMax,
            string title,
            Color toneColor
        )
        {
            m_dStepZ = timeStepMs / 1000.0;

            bool bLogFreqAxis = false;
            double dDefaultYMax = 500;

            //Create chart 
            _chart = new LightningChartUltimate();
            _chart.HorizontalAlignment = HorizontalAlignment.Left;
            _chart.VerticalAlignment = VerticalAlignment.Top;

            parentControl.Children.Add(_chart);

            _chart.BeginUpdate();

            _chart.ActiveView = ActiveView.View3D;

            _chart.ChartBackground.Style = RectFillStyle.ColorOnly;
            _chart.ChartBackground.Color = Color.FromArgb(0, 0, 0, 0);
            _chart.ChartBackground.GradientColor = Color.FromArgb(0, 0, 0, 0);
            _chart.ChartBackground.GradientFill = GradientFill.Solid;
            _chart.Title.Text = title;
            _chart.Title.Font = new WpfFont("Segoe UI", 14, true, false);
            _chart.Title.Offset.SetValues(0, 20);


            _chart.View3D.WallOnBack.Visible = true;
            _chart.View3D.WallOnBack.Material.AmbientColor = Colors.Transparent;
            _chart.View3D.WallOnBack.Material.DiffuseColor = Colors.Black;
            _chart.View3D.WallOnBack.Material.EmissiveColor = Colors.Black;
            _chart.View3D.WallOnBack.Material.SpecularColor = Colors.Black;
            _chart.View3D.WallOnLeft.Visible = true;
            _chart.View3D.WallOnLeft.Material.AmbientColor = Colors.Transparent;
            _chart.View3D.WallOnLeft.Material.DiffuseColor = Colors.Black;
            _chart.View3D.WallOnLeft.Material.EmissiveColor = Colors.Black;
            _chart.View3D.WallOnLeft.Material.SpecularColor = Colors.Black;
            _chart.View3D.WallOnRight.Visible = true;
            _chart.View3D.WallOnRight.Material.AmbientColor = Colors.Transparent;
            _chart.View3D.WallOnRight.Material.DiffuseColor = Colors.Black;
            _chart.View3D.WallOnRight.Material.EmissiveColor = Colors.Black;
            _chart.View3D.WallOnRight.Material.SpecularColor = Colors.Black;
            _chart.View3D.WallOnBottom.Visible = true;
            _chart.View3D.WallOnBottom.Material.AmbientColor = Colors.Transparent;
            _chart.View3D.WallOnBottom.Material.DiffuseColor = Colors.Black;
            _chart.View3D.WallOnBottom.Material.EmissiveColor = Colors.Black;
            _chart.View3D.WallOnBottom.Material.SpecularColor = Colors.Black;
            _chart.View3D.WallOnTop.Visible = false;
            _chart.View3D.WallOnFront.Visible = false;

            _chart.View3D.XAxisPrimary3D.Orientation = PlaneXAxis3D.XY;
            if (bLogFreqAxis)
            {
                _chart.View3D.XAxisPrimary3D.ScaleType = ScaleType.Logarithmic;
                if (freqMin < 1)
                    freqMin = 1;
            }
            _chart.View3D.XAxisPrimary3D.CornerAlignment = AxisAlignment3D.Outside;
            _chart.View3D.XAxisPrimary3D.MajorDivTickStyle.Alignment = Alignment.Far;
            _chart.View3D.XAxisPrimary3D.LabelsColor = Color.FromArgb(200, 255, 255, 255);
            _chart.View3D.XAxisPrimary3D.LabelsFont = new WpfFont("Segoe UI", 13, false, false);
            _chart.View3D.XAxisPrimary3D.MajorDivTickStyle.Color = Colors.Orange;
            _chart.View3D.XAxisPrimary3D.Title.Text = "频率(Hz)";
            _chart.View3D.XAxisPrimary3D.Title.Font = new WpfFont("Segoe UI", 13, true, false);

            _chart.View3D.YAxisPrimary3D.Orientation = PlaneYAxis3D.XY;
            _chart.View3D.YAxisPrimary3D.CornerAlignment = AxisAlignment3D.Outside;
            _chart.View3D.YAxisPrimary3D.MajorDivTickStyle.Alignment = Alignment.Far;
            _chart.View3D.YAxisPrimary3D.LabelsColor = Color.FromArgb(200, 255, 255, 255);
            _chart.View3D.YAxisPrimary3D.LabelsFont = new WpfFont("Segoe UI", 13, false, false);
            _chart.View3D.YAxisPrimary3D.MajorDivTickStyle.Color = Colors.Orange;
            _chart.View3D.YAxisPrimary3D.Title.Text = "幅值";
            _chart.View3D.YAxisPrimary3D.Title.Font = new WpfFont("Segoe UI", 13, true, false);
            _chart.View3D.YAxisPrimary3D.SetRange(0, dDefaultYMax);

            _chart.View3D.ZAxisPrimary3D.Reversed = true;
            _chart.View3D.ZAxisPrimary3D.LabelsColor = Color.FromArgb(200, 255, 255, 255);
            _chart.View3D.ZAxisPrimary3D.LabelsFont = new WpfFont("Segoe UI", 13, false, false);
            _chart.View3D.ZAxisPrimary3D.Title.Text = "时间";
            _chart.View3D.ZAxisPrimary3D.ValueType = AxisValueType.Time;
            _chart.View3D.ZAxisPrimary3D.MajorDivTickStyle.Color = Colors.Orange;
            _chart.View3D.ZAxisPrimary3D.Title.Font = new WpfFont("Segoe UI", 13, true, false);
            _chart.View3D.WallOnBottom.GridStrips = WallGridStripXZ.X;

            _chart.View3D.LegendBox.SeriesTitleColor = Colors.White;
            _chart.View3D.LegendBox.ValueLabelColor = Colors.White;
            _chart.View3D.LegendBox.SurfaceScales.ScaleBorderColor = Colors.White;
            _chart.View3D.LegendBox.Position = LegendBoxPosition.TopRight;
            _chart.View3D.LegendBox.Offset.SetValues(0, 0);
            _chart.View3D.LegendBox.Fill.Style = RectFillStyle.None;
            _chart.View3D.LegendBox.Shadow.Visible = false;
            _chart.View3D.LegendBox.BorderWidth = 0;

            _chart.View3D.Camera.RotationX = 20;
            _chart.View3D.Camera.RotationY = -30;
            _chart.View3D.Camera.RotationZ = 0;
            _chart.View3D.Camera.Target.SetValues(-9, -18, 2);

            _chart.View3D.Camera.MinimumViewDistance = 10;
            _chart.View3D.Camera.ViewDistance = 200;

            _chart.ChartName = "Spectrum 3D chart";

            double dAxisZMin = timeStepMs / 1000.0 - timeRangeLengthSec;
            double dAxisZMax = 0;
            m_dStepZ = timeStepMs / 1000.0;
            _chart.View3D.ZAxisPrimary3D.SetRange(dAxisZMin, dAxisZMax);
            _chart.View3D.XAxisPrimary3D.SetRange(freqMin, freqMax);
            _chart.View3D.ZAxisSecondary3D.SetRange(0, 1000);

            m_dCurrentZ = 0;

            //Create surface grid series
            m_surface = new SurfaceGridSeries3D(
                _chart.View3D,
                Axis3DBinding.Primary,
                Axis3DBinding.Primary,
                Axis3DBinding.Primary);

            m_surface.ContourPalette = CreatePalette(m_surface, dDefaultYMax);

            _chart.View3D.SurfaceGridSeries3D.Add(m_surface);

            m_surface.InitialValue = 0;
            m_surface.WireframeType = SurfaceWireframeType3D.None;
            m_surface.ContourLineType = ContourLineType3D.None;
            m_surface.SetSize(resolution, (int)Math.Round(timeRangeLengthSec / (timeStepMs / 1000.0)));
            m_surface.FadeAway = 100; //消退属性
            m_surface.SuppressLighting = false;
            m_surface.SetRangesXZ(freqMin, freqMax, -timeRangeLengthSec, 0);
            m_surface.ShowInLegendBox = false;
            m_surface.Material.EmissiveColor = Color.FromArgb(255, 0, 40, 0);
            m_surface.BaseColor = Colors.White;

            //Create Waterfall series to front
            m_waterfallFront = new WaterfallSeries3D(
                _chart.View3D,
                Axis3DBinding.Primary,
                Axis3DBinding.Primary,
                Axis3DBinding.Primary);
            m_waterfallFront.ContourPalette = CreatePalette(m_waterfallFront, dDefaultYMax);
            _chart.View3D.WaterfallSeries3D.Add(m_waterfallFront);
            m_waterfallFront.SetSize(resolution, 1);
            m_waterfallFront.BaseLevel = _chart.View3D.YAxisPrimary3D.Minimum;
            m_waterfallFront.FadeAway = 0;
            m_waterfallFront.SuppressLighting = false;
            m_waterfallFront.Material.EmissiveColor = Color.FromArgb(255, 0, 40, 0);
            m_waterfallFront.BaseColor = Colors.White;
            m_waterfallFront.ShowInLegendBox = false;
            
            //Init one row
            SurfacePoint[,] areaData = m_waterfallFront.Data;
            int iColCount = m_waterfallFront.SizeX;
            double x = _chart.View3D.XAxisPrimary3D.Minimum;
            double stepX;
            if (iColCount > 1)
                stepX = (_chart.View3D.XAxisPrimary3D.Maximum - _chart.View3D.XAxisPrimary3D.Minimum) / (double)(iColCount - 1);
            else
                stepX = 0;
            double z = _chart.View3D.ZAxisSecondary3D.Maximum;
            double y = m_waterfallFront.InitialValue;
            for (int iCol = 0; iCol < iColCount; iCol++)
            {
                areaData[iCol, 0].X = x;
                areaData[iCol, 0].Y = y;
                areaData[iCol, 0].Z = z;
                x += stepX;
            }
            m_waterfallFront.InvalidateData();
            m_waterfallFront.ContourLineType = WaterfallContourLineType.None;
            m_waterfallFront.WireframeType = WaterfallWireframeType.WireframePalettedByY;
            _chart.EndUpdate();
        }

        public LightningChartUltimate Chart
        {
            get
            {
                return _chart;
            }
        }

        public string GetFileName()
        {
            return GetFileNameFromStackFrame();
        }

        private string GetFileNameFromStackFrame()
        {
            System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame(1, true);
            string stFileName = stackFrame.GetFileName();

            return stFileName;
        }

        ValueRangePalette CreatePalette(SeriesBase3D ownerSeries, double yRange)
        {
            ValueRangePalette palette = new ValueRangePalette(ownerSeries);

            palette.Steps.Clear();
            palette.Steps.Add(new PaletteStep(palette, Colors.Transparent, 0));
            palette.Steps.Add(new PaletteStep(palette, Colors.Blue, 20 * yRange / 100.0));
            palette.Steps.Add(new PaletteStep(palette, Colors.Green, 60.0 * yRange / 100.0));
            palette.Steps.Add(new PaletteStep(palette, Colors.Yellow, 100.0 * yRange / 100.0));
            palette.Type = PaletteType.Gradient;
            return palette;
        }

        public void Dispose()
        {
            if (m_surface != null)
            {
                m_surface.Dispose();
                m_surface = null;
            }

            if (m_waterfallFront != null)
            {
                m_waterfallFront.Dispose();
                m_waterfallFront = null;
            }

            if (_chart != null)
            {
                _chart.Dispose();
                _chart = null;
            }
        }

        public void FitView()
        {
            int iColumns = m_surface.SizeX;
            int rows = m_surface.SizeZ;
            SurfacePoint[,] data = m_surface.Data;
            double minY = double.MaxValue;
            double maxY = double.MinValue;
            double y;
            _chart.BeginUpdate();
            if (m_surface != null)
            {
                for (int iCol = 0; iCol < iColumns; iCol++)
                {
                    for (int row = 0; row < rows; row++)
                    {
                        y = data[iCol, row].Y;
                        if (y > maxY)
                            maxY = y;
                        if (y < minY)
                            minY = y;
                    }
                }
                m_surface.ContourPalette = CreatePalette(m_surface, maxY);
                _chart.View3D.YAxisPrimary3D.SetRange(0, maxY);
            }
            if (m_waterfallFront != null)
            {
                m_waterfallFront.ContourPalette = CreatePalette(m_waterfallFront, maxY);
            }
            _chart.EndUpdate();
        }

        public void SetData(double[][][] yValues, int channelIndex, int rowCount, double speed)
        {
            if (_chart == null)
                return;

            _chart.BeginUpdate();

            for (int row = 0; row < rowCount; row++)
            {
                double[] aYValues = yValues[row][channelIndex];
                m_dCurrentZ += m_dStepZ;
                double dZMin = m_dCurrentZ - (_chart.View3D.ZAxisPrimary3D.Maximum - _chart.View3D.ZAxisPrimary3D.Minimum);
                double dZMax = m_dCurrentZ;

                m_surface.InsertRowBackAndScroll(aYValues, dZMin, dZMax, dZMin, dZMax);
                SurfacePoint[,] areaData = m_waterfallFront.Data;
                int iColCount = m_waterfallFront.SizeX;
                int iNewDataCopyCount = Math.Min(aYValues.Length, iColCount);

                for (int iCol = 0; iCol < iNewDataCopyCount; iCol++)
                {
                    areaData[iCol, 0].Y = aYValues[iCol];
                    areaData[iCol, 0].Z = m_dCurrentZ;
                }
            }

            m_waterfallFront.InvalidateData();
            _chart.View3D.ZAxisSecondary3D.SetRange(0, speed);
            _chart.EndUpdate();
        }

        public void SetBounds()
        {
            _chart.Margin = new Thickness(10);
            _chart.HorizontalAlignment = HorizontalAlignment.Stretch;
            _chart.VerticalAlignment = VerticalAlignment.Stretch;
        }
    }
}
